package org.digitarts.rtms.spring.collectors;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.management.ObjectName;

import org.apache.log4j.Logger;
import org.digitarts.rtms.helpers.StringHelper;
import org.digitarts.rtms.spring.collectors.cache.CacheConfiguration;
import org.digitarts.rtms.tracing.ITracer;
import org.digitarts.rtms.tracing.TracerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;

@ManagedResource
public class CollectorBase implements ICollector, BeanNameAware, BeanFactoryAware
{
	protected Logger					log							= null;
	protected ITracer					tracer						= TracerFactory.getInstance();
	protected String[]					tracingNameSpace			= new String[0];
	protected String					collectorType				= null;
	protected ObjectName				objectName					= null;
	protected boolean					logErrors					= false;
	protected long						collectionFrequency			= 15000L;
	protected long						initialDelay				= -1L;
	protected long						lastElapsed					= 0L;
	protected long						totalCollections			= 0L;
	protected long						totalCollectionExceptions	= 0L;
	protected long						lastSuccessfulCollection	= 0L;
	protected long						lastCollectionAttempt		= 0L;
	protected Map<String, Object>		configuration				= new HashMap<String, Object>();
	protected ScheduledExecutorService	scheduler					= null;
	protected CollectorState			state						= CollectorState.NULL;
	protected ScheduledFuture<?>		scheduledTask				= null;
	protected String					beanName					= null;
	protected BeanFactory				beanFactory					= null;
	protected String[]					elapsedTimeMetricNameSpace	= null;
	protected CacheConfiguration		cacheConfiguration			= null;
	protected int						cacheCount					= 0;

	public static final Pattern			namePattern					= Pattern.compile("\\{(\\d++)\\}");
	public static final Pattern			targetPattern				= Pattern.compile("\\{TARGET-PROPERTY:([a-zA-Z\\(\\)\\s-]+)}", Pattern.CASE_INSENSITIVE);
	public static final Pattern			thisPattern					= Pattern.compile("\\{THIS-PROPERTY:([a-zA-Z\\(\\)\\s-]+)}", Pattern.CASE_INSENSITIVE);
	public static final Pattern			targetDomainPattern			= Pattern.compile("\\{TARGET-DOMAIN:([\\d+])}", Pattern.CASE_INSENSITIVE);
	public static final Pattern			thisDomainPattern			= Pattern.compile("\\{THIS-DOMAIN:([\\d+])}", Pattern.CASE_INSENSITIVE);
	public static final Pattern			segmentPattern				= Pattern.compile("\\{TRN:([\\d+])}", Pattern.CASE_INSENSITIVE);

	public CollectorBase()
	{
		log = Logger.getLogger(getClass());
		state = CollectorState.CONSTRUCTED;
		log.info("Created Instance of " + this.getClass().getName());
	}

	public final void collect()
	{
		long start = System.currentTimeMillis();
		try
		{
			lastCollectionAttempt = System.currentTimeMillis();
			state = CollectorState.COLLECTING;
			collectCallback();
			lastElapsed = System.currentTimeMillis() - start;
			totalCollections++;
			lastSuccessfulCollection = System.currentTimeMillis();
		}
		catch (Exception e)
		{
			totalCollectionExceptions++;
		}
		finally
		{
			state = CollectorState.STARTED;
		}
	}

	protected void collectCallback() throws Exception
	{

	}

	public boolean isLogErrors()
	{
		return logErrors;
	}

	public void setLogErrors(boolean b)
	{
		logErrors = b;
	}

	@ManagedAttribute(description="The underlying collector type")
	public String getCollectorType()
	{
		return collectorType;
	}

	@ManagedAttribute(description="The frequency of collections in ms")
	public long getFrequency()
	{
		return collectionFrequency;
	}

	@ManagedAttribute(description="The JMX ObjectName that the collector will be registered as")
	public ObjectName getObjectName()
	{
		return objectName;
	}

	public final void init()
	{
		try
		{
			if (initialDelay == -1L)
			{
				initialDelay = collectionFrequency;
			}
			state = CollectorState.INITING;
			formatSegments(tracingNameSpace);
			elapsedTimeMetricNameSpace = StringHelper.append(tracingNameSpace, "Collection Time (ms)");
			prepareCaches();
			initCallback();
			state = CollectorState.INITED;
		}
		catch (Exception e)
		{
			state = CollectorState.START_FAILED;
		}
	}

	protected void prepareCaches()
	{
		if (cacheConfiguration != null)
		{
			tracer = cacheConfiguration.generateCachingTracer(tracer);
		}

	}

	public void springStart()
	{
		init();
		start();
	}

	public void initCallback()
	{
	}

	public void postCollect()
	{
	}

	public void preCollect()
	{
	}

	public void setFrequency(long frequency)
	{
		collectionFrequency = frequency;
	}

	public void setGlobalConfiguration(Map<String, Object> config)
	{
		configuration.putAll(config);

	}

	public void setObjectName(ObjectName objectName)
	{
		this.objectName = objectName;
	}

	public void setScheduler(ScheduledExecutorService scheduler)
	{
		this.scheduler = scheduler;

	}

	@ManagedOperation(description="Collector start callback")
	public final void start()
	{
		try
		{
			state = CollectorState.STARTING;
			startCallback();
			if (collectionFrequency > 0)
			{
				scheduledTask = scheduler.scheduleWithFixedDelay(this, initialDelay, collectionFrequency, TimeUnit.MILLISECONDS);
				log.info("Scheduled [" + objectName + "] Collection with Frequency:" + collectionFrequency);
				state = CollectorState.STARTED;
			}
			else
			{
				state = CollectorState.INITED;
				log.info("Collector [" + objectName + "] Initialized With No Schedule");
			}
			log.info("Collector [" + objectName + "] Acquired State [" + state.name() + "]");
		}
		catch (Exception e)
		{
			state = CollectorState.START_FAILED;
		}
	}

	public void startCallback() throws Exception
	{

	}

	@ManagedOperation(description="Collector stop callback")
	public void stop()
	{
		try
		{
			state = CollectorState.STOPPING;
			stopCallback();
			if (scheduledTask != null)
			{
				scheduledTask.cancel(true);
				log.info("Cancelled Collection for [" + objectName + "]");
			}
		}
		catch (Exception e)
		{

		}
		finally
		{
			state = CollectorState.STOPPED;
		}

	}

	public void stopCallback() throws Exception
	{

	}

	@ManagedAttribute(description="The tracing name space prefix for this collector")
	public String[] getTracingNameSpace()
	{
		return tracingNameSpace;
	}

	public void setTracingNameSpace(String[] nameSpace)
	{
		tracingNameSpace = nameSpace;
	}

	@ManagedAttribute(description="The time stamp of the last successful collection")
	public long getLastSuccessfulCollection()
	{
		return lastSuccessfulCollection;
	}

	@ManagedAttribute(description="The time stamp of the last collection attempt")
	public long getLastCollectionAttempt()
	{
		return lastCollectionAttempt;
	}

	public CollectorState getState()
	{
		return state;
	}

	@ManagedAttribute(description="The state of the collector")
	public String getStateName()
	{
		return state.name();
	}

	public void run()
	{
		try
		{
			preCollect();
			collect();
		}
		catch (Exception e)
		{
			if (logErrors)
				log.warn("Failed to issue collect", e);
		}
		finally
		{
			try
			{
				postCollect();
			}
			catch (Exception e2)
			{
				if (logErrors)
					log.warn("Failed to issue postCollect", e2);
			}

		}
	}

	@ManagedAttribute(description="The initial delay before the first collection after collector start")
	public long getInitialDelay()
	{
		return initialDelay;
	}

	public void setInitialDelay(long initialDelay)
	{
		this.initialDelay = initialDelay;
	}

	@ManagedAttribute(description="The elapsed time of the last successful collection")
	public long getLastElapsed()
	{
		return lastElapsed;
	}

	@ManagedAttribute(description="The spring bean name")
	public String getBeanName()
	{
		return beanName;
	}

	public void setBeanName(String beanName)
	{
		this.beanName = beanName;
		log = Logger.getLogger(getClass().getName() + "." + beanName);

	}

	public void setBeanFactory(BeanFactory beanFactory)
	{
		this.beanFactory = beanFactory;
	}

	protected String formatField(final String template, String[] data)
	{
		String newTemplate = new String(template);
		if (template == null)
			return "";
		if (template.startsWith("{") && template.endsWith("}"))
		{
			if (template.equalsIgnoreCase("{beanName}"))
			{
				return beanName;
			}
			Matcher m = namePattern.matcher(template);
			if (m.matches())
			{
				m.group();
				try
				{
					int index = Integer.parseInt(m.group(1));
					newTemplate = data[index];
				}
				catch (Exception e)
				{
					newTemplate = "NameSpace Error";
				}
			}
			else
			{
				newTemplate = "NameSpace Token Error";
			}
		}
		return newTemplate;
	}

	public void formatSegments(String[] fragments)
	{
		if (fragments == null || fragments.length < 1)
			return;
		for (int i = 0; i < fragments.length; i++)
		{
			fragments[i] = formatField(fragments[i], null);
		}
	}

	protected String formatName(String name)
	{
		if (name.toUpperCase().contains("{THIS-PROPERTY"))
		{
			name = bindTokens(objectName, name, thisPattern);
		}
		if (name.toUpperCase().contains("{THIS-DOMAIN"))
		{
			name = bindTokens(objectName, name, thisDomainPattern);
		}
		if (name.toUpperCase().contains("{TRN"))
		{
			name = bindTokens(objectName, name, segmentPattern);
		}
		return name;
	}

	protected String formatName(String name, ObjectName remoteMBean)
	{
		if (name.equalsIgnoreCase("{beanName}"))
		{
			return beanName;
		}
		else if (name.toUpperCase().contains("{TARGET-PROPERTY"))
		{
			return bindTokens(remoteMBean, name, targetPattern);
		}
		else if (name.toUpperCase().contains("{TARGET-DOMAIN"))
		{
			return bindTokens(remoteMBean, name, targetDomainPattern);
		}
		else if (name.toUpperCase().contains("{THIS-PROPERTY"))
		{
			return bindTokens(objectName, name, thisPattern);
		}
		else if (name.toUpperCase().contains("{THIS-DOMAIN"))
		{
			return bindTokens(objectName, name, thisDomainPattern);
		}
		else if (name.toUpperCase().contains("{TRN"))
		{
			return bindTokens(objectName, name, segmentPattern);
		}
		else
		{
			return name;
		}

	}

	public String bindTokens(ObjectName targetObjectName, String text, Pattern p)
	{
		Matcher matcher = p.matcher(text);
		String token = null;
		String property = null;
		String propertyValue = null;
		int pos = -1;
		while (matcher.find())
		{
			token = matcher.group(0);
			property = matcher.group(1);
			propertyValue = targetObjectName.getKeyProperty(property);
			if (token.toUpperCase().contains("DOMAIN"))
			{
				pos = Integer.parseInt(property);
				propertyValue = targetObjectName.getDomain().split("\\.")[pos];
			}
			else if (token.toUpperCase().contains("TRN"))
			{
				pos = Integer.parseInt(property);
				propertyValue = tracingNameSpace[pos];
			}
			else
			{
				propertyValue = targetObjectName.getKeyProperty(property);
			}
			text = text.replace(token, propertyValue);
		}
		return text;
	}

	public String bindTokens(Properties targetProperties, String text, Pattern p)
	{
		Matcher matcher = p.matcher(text);
		String token = null;
		String property = null;
		String propertyValue = null;
		while (matcher.find())
		{
			token = matcher.group(0);
			property = matcher.group(1);
			propertyValue = targetProperties.getProperty(property);
			text = text.replace(token, propertyValue);
		}
		return text;
	}

	public void setCacheConfiguration(CacheConfiguration cacheConfiguration)
	{
		this.cacheConfiguration = cacheConfiguration;
	}

}
